#ifndef BANANALOADER_H
#define BANANALOADER_H

#include <avr/io.h>
#include <stdint.h>
#include <avr/boot.h>
#include <avr/pgmspace.h>
#include <util/delay.h>

#include "rs232.h"

/*
 * Dla procesorow majacych dwa USARTY
 * nalezy przeslonic nazwy rejestrow
 * i bitow, by nie zmieniac kodu
 */
#include "defines.h"

/**
 * Tablica, z której korzysta funkcja Get_Byte_From_Buffer(). Jest globalna, bo informacje w niej przechowywane są używane przez wiele wywołań funkcji. Tablica przechowująca pobraną linię
 */
uint8_t BUFOR[16];

/**
 * Zmienna, z której korzysta funkcja Get_Byte_From_Buffer(). Jest globalna, bo informacje w niej przechowywane są używane przez wiele wywołań funkcji. Na początku linii w pliku hex jest podane ile bajtow będzie ona zawierać. Przechowujemy tą informację w tej zmiennej.
 */
uint8_t ilosc;

/**
 * Zmienna, z której korzysta funkcja Get_Byte_From_Buffer(). Zmienna pomocnicza.
 */
uint8_t tmp;

/**
 * Zmienna, z której korzysta funkcja Get_Byte_From_Buffer(). Jest globalna, bo informacje w niej przechowywane są używane przez wiele wywołań funkcji. Na końcu pliku hex znajduje się informacja, że to rzeczywiście jego koniec. Po odczytaniu tej informacji zmienna przechowuje wartość niezerową.
 */
uint8_t koniec;

/**
 * Zmienna, z której korzysta funkcja Get_Byte_From_Buffer(). Jest globalna, bo informacje w niej przechowywane są używane przez wiele wywołań funkcji. Przechowuje aktualny numer bajtu w linii, który zostanie zwrócony przez Get_Byte_From_Buffer().
 */
uint8_t aktualny;

/**
 * Funkcja przyjmuje znak odpowiadający liczbie szesnastkowej i konwertuje go na typ int.
 *
 * @param uint8_t
 * @return uint8_t
 */
uint8_t char_to_hex(const uint8_t);

/**
 * Najważnejsza funkcja programu. Zwraca bajt pobrany z USART. Zanim to zrobi, pobiera całą linię i usuwa niepotrzebne dane jak adres, typ, suma kontrolna.
 */
uint8_t Get_Byte_From_Buffer(void);

/**
 * Funkcja pobiera dwa znaki z UART i konwertuje je na liczbę całkowitą.
 * @param void
 * @return uint8_t
 */
uint8_t USART_Get_Byte(void);

/**
 * Zawartość tej funkcji jest zapisana w sekcji .vectors. Wykonuje się w niej skok do .init2
 */
static void
__vectors(void)
__attribute__ (( section( ".vectors" ), naked, used ));


/**
 * Zawartość tej funkcji jest zapisana w sekcji .init2. Funkcja służy do inicjalizacji stosu.
 */
static void
__init2(void)
__attribute__ (( section( ".init2" ), naked, used ));


/**
 * Zawartość tej funkcji jest zapisana w sekcji .init9. W tej funkcji skaczemy do funkcji main().
 */
static void
__init9(void)
__attribute__ (( section( ".init9" ), naked, used ));


/**
 * Funkcja typu inline dla zwiększenia przejrzystości funkcji main(). Jej zadaniem jest wymazanie całej pamięci programu.
 */
inline void Erase_All_Memory(void);

/**
 * Funkcja typu inline dla zwiększenia przejrzystości funkcji main(). Funkcja pobiera kolejne bajty za pomocą Get_Byte_From_Buffer() i zapisuje je do pamięci.
 */
inline void Memory_Programming(void);

/**
 * Funkcja typu inline dla zwiększenia przejrzystości funkcji main(). Jedyne jej zadane to skok pod adres 0. Jest wykonywana po zakończeniu programowania. Cala funkcja jest napisana w asemblerze.
 */
inline void jump0(void);


/* Funkcja kasujaca cala pamiec programu*/
inline void Erase_All_Memory(void)
{
    for ( uint32_t Address = 0;
            Address < BLS_START;
            Address += SPM_PAGESIZE )
    {

        /* Czyszczenie strony o danym adresie */
        boot_page_erase( Address );

        /* Oczekiwanie na zakonczenie opracji na pamieci */
        boot_spm_busy_wait();
    }
}

inline void Memory_Programming(void)
{
    uint16_t Instruction;

    /* Glowna petla programujaca */
    for ( uint32_t Page_Address = 0;
            Page_Address < BLS_START;
            Page_Address += SPM_PAGESIZE )
    {

        /* zapelnienie bufora strony */
        for ( uint16_t Byte_Address = 0;
                Byte_Address < SPM_PAGESIZE;
                Byte_Address += 2 )
        {

            /* przygotowanie instrukcji */
            Instruction = (uint16_t )Get_Byte_From_Buffer();
            Instruction += Get_Byte_From_Buffer() << 8;

            /* zapisanie instrukcji do bufora */
            boot_page_fill( Byte_Address, Instruction );
        }

        /* zapisanie strony pami鹹i */
        boot_page_write( Page_Address );
        boot_spm_busy_wait();
        if (koniec) break;
    }
}

inline void jump0(void)
{
#ifdef __AVR_HAVE_JMP_CALL__
    asm volatile (
        "jmp 0" "\n\t"
    : : );
#else
    asm volatile (
        "clr r30" "\n\t"
        "clr r31" "\n\t"
        "ijmp" "\n\t"
    : : );
#endif
}

#endif
